#!/usr/bin/env python

# stderr, stdout, ...
from sys import *

# xpermutations
import _algo

###########################################################

class Hazards:
    NONE = 0
    """= Bog =
    - Native: Ogr Tro Ran Wyv Hyd
    - Entry forbidden to non-native, non-flying characters. Flying non-native character may not end its move on Bog.
    """
    BOG = 1
    """= Bramble =
    - Native: @todo documentation, implementation
    - @todo documentation, implementation
    """
    BRAMBLE = 2

HAZARD_TO_NAME = {
    Hazards.NONE     : "",
    Hazards.BOG      : "BOG",
    Hazards.BRAMBLE  : "BRAMBLE",
    }

def hazardToName(hz):
    return HAZARD_TO_NAME[hz]

###########################################################

class Terrain_Base(object):
    HEXCOUNT = 33 # [0..32], where some are virtual and not on the actual battefield
    def __init__(self, name):
        self._name = name
        self._countHexesInside = 27 # [0..26]
        self._moves = self._plainsMovesDict()
        self._hazards = [ Hazards.NONE ] * self.HEXCOUNT

    def getName(self):
        return self._name

    def _plainsMovesDict(self):
        """
        ### hex representation
        ###
        #          +--+               29    +--+   30
        #       +--+d6+--+               +--+17+--+
        #    +--+c5+--+e5+--+         +--+11    22+--+
        # +--+b4+--+  +--+f4|      +--+6     16    26|
        # |a3+--+        +--+      |2     10    21   +
        # +--+  +        |  |      +   5     15    25|
        # |  |  +        |  |   28 |1     9     20   | 31
        # +--+  +        |  |      +   4     14    24|
        # |a1+--+        +--+      |0     8     19   +
        # +--+b1+--+  +--+f1|      +--+3     13    23|
        #    +--+c1+--+e1+--+         +--+7     18+--+
        #       +--+d1+--+            27 +--+12+--+
        #          +--+                     +--+   32
        """
        # easy dict representation
        moves = dict( [ (i,[])  for i in range(self.HEXCOUNT) ] ) # clear
        moves[  0] = frozenset([  1,  3,  4 ])
        moves[  1] = frozenset([  0,  2,  5,  4])
        moves[  2] = frozenset([  1,  5,  6 ])
        moves[  3] = frozenset([  0,  4,  8, 7])
        moves[  4] = frozenset([  0,  1,  5,  9,  8,  3 ])
        moves[  5] = frozenset([  1,  2,  6, 10,  9,  4 ])
        moves[  6] = frozenset([  2,  5, 10, 11 ])
        moves[  7] = frozenset([  3,  8, 13, 12 ])
        moves[  8] = frozenset([  3,  4,  9, 14, 13,  7 ])
        moves[  9] = frozenset([  4,  5, 10, 15, 14,  8 ])
        moves[ 10] = frozenset([  5,  6, 11, 16, 15,  9 ])
        moves[ 11] = frozenset([  6, 10, 16, 17 ])
        moves[ 12] = frozenset([  7, 13, 18 ])
        moves[ 13] = frozenset([  7,  8, 14, 19, 18, 12 ])
        moves[ 14] = frozenset([  8,  9, 15, 20, 19, 13 ])
        moves[ 15] = frozenset([  9, 10, 16, 21, 20, 14 ])
        moves[ 16] = frozenset([ 10, 11, 17, 22, 21, 15 ])
        moves[ 17] = frozenset([ 11, 16, 22 ])
        moves[ 18] = frozenset([ 12, 13, 19, 23 ])
        moves[ 19] = frozenset([ 13, 14, 20, 24, 23, 18 ])
        moves[ 20] = frozenset([ 14, 15, 21, 25, 24, 19 ])
        moves[ 21] = frozenset([ 15, 16, 22, 26, 25, 20 ])
        moves[ 22] = frozenset([ 17, 16, 21, 26])
        moves[ 23] = frozenset([ 18, 19, 24 ])
        moves[ 24] = frozenset([ 25, 20, 19, 23 ])
        moves[ 25] = frozenset([ 26, 21, 20, 24 ])
        moves[ 26] = frozenset([ 22, 21, 25 ])
        moves[ 27] = frozenset([  0,  3,  7, 12])
        moves[ 28] = frozenset([  0,  1,  2])
        moves[ 29] = frozenset([  2,  6, 11, 17])
        moves[ 30] = frozenset([ 17, 22, 26  ])
        moves[ 31] = frozenset([ 26, 25, 24, 23 ])
        moves[ 32] = frozenset([ 12, 18, 23 ])
        return moves

    def getNeighbourHexes(self, hex):
        """get potential neighbours for striking and moving. each case might have to be checked further.
        also, this does not include neighbours that hex can not reach, i.e. an outside hex from an inside hex.
        """
        return self._moves[hex]

    def isOnfield(self, hex):
        return 0 <= hex < self._countHexesInside

    def isHexMoveoutIfPossible(self, hex):
        return not self.isOnfield(hex)

    def couldStep(self, fromHex, toHex):
        """basic Plains implementation is easy. refine more for other terrain."""
        return toHex in self.getNeighbourHexes(fromHex)

    def couldStrike(self, fromHex, toHex):
        r = ( self.isOnfield(fromHex) and self.isOnfield(toHex) and self.couldStep(fromHex, toHex) )
        return r

    def getHazardOfHex(self, hex):
        return self._hazards[hex]

    def pretty(self,  army1,  army2):
        """prettyprint"""
        ts = [
"           .          .          +----------+          .          .",
"                   29            |       17 |                  30  ",
".          .          .          |          |                      ",
"        28            +----------+          +----------+           ",
"                      |       11 |          |       22 |           ",
"           .          |          | d6       |          | x4        ",
"           +----------+          +----------+          +----------+",
"           |        6 |          |       16 |          |       26 |",
"           |          | c5       |          | e5       |          |",
"+----------+          +----------+          +----------+          |",
"|        2 |          |       10 |          |       21 |          |",
"|          | b4       |          | d5       |          | f4       |",
"|          +----------+          +----------+          +----------+",
"|          |        5 |          |       15 |          |       25 |",
"| a3       |          | c4       |          | e4       |          |",
"+----------+          +----------+          +----------+          |.         .",
"|        1 |          |        9 |          |       20 |          |       31  ",
"|          | c3       |          | d4       |          | f3       |           ",
"|          +----------+          +----------+          +----------+           ",
"|          |        4 |          |       14 |          |       24 |           ",
"| a2       |          | c3       |          | e3       |          |           ",
"+----------+          +----------+          +----------+          |.         .",
"|        0 |          |        8 |          |       19 |          |",
"|          | b2       |          | d3       |          | f2       |",
"|          +----------+          +----------+          +----------+",
"|          |        3 |          |       13 |          |       23 |",
"| a1       |          | c2       |          | e2       |          |",
"+----------+          +----------+          +----------+          |",
"           |          |        7 |          |       18 |          |",
"           | b1       |          | d2       |          | f1       |",
"           +----------+          +----------+          +----------+",
"                   27 |          |       12 |          |       32 .",
"                      | c1       |          | e1       |           ",
"                      +----------+          +----------+           ",
"                                 |          |                      ",
"             x1                  | d1       |                      ",
"           .          .          +----------+          .          .",
]
        # critters
        if army1:
            for crit in army1._critters:
                ts = self._prettyPlaceCritter(ts, crit, isArmy1=True)
        if army2:
            for crit in army2._critters:
                ts = self._prettyPlaceCritter(ts, crit, isArmy1=False)
        # hazards
        for hex in range(self.HEXCOUNT):
            hz = self.getHazardOfHex(hex)
            if hz != Hazards.NONE:
                ts = self._prettyPlaceHazard(ts, hz, hex)
        return "\n".join(ts)

    def _prettyPlaceHazard(self, ts, hz, hex):
        shex = " %2d " % hex # " _4 "   # len(shex) == 4
        ti, x = 0, -1
        for t in ts:
            x = t.find(shex)
            if x >= 0:
                break
            ti += 1
        if x < 0: raise "hex:'%s' not found in template" % shex
        y = 3
        # do placing
        t = ts[ti+y]
        _hs = hazardToName(hz)
        f = _hs.center(8, "*")  # f must have length 8
        t = t[:x-4-1] + f + t[x+(8-4-1):]   # 8=len(f), 4=len(shex), -1=relative_writepos
        ts[ti+y] = t
        return ts


    def _prettyPlaceCritter(self, ts, crit, isArmy1):
        shex = " %2d " % crit._hex # " _4 "   # len(shex) == 4
        ti, x = 0, -1
        for t in ts:
            x = t.find(shex)
            if x >= 0:
                break
            ti += 1
        if x < 0: raise "hex:'%s' not found in template" % shex
        y = 1
        while ts[ti+y][x-4] != ' ':  # '-4' char written = len(shex)
            y += 1
        # do placing
        t = ts[ti+y]
        _c1, _c2 = ("[", "]")  if isArmy1 else (".", ".")
        _schaden = crit._init_power - crit._power
        _d = "-%d" % _schaden   if _schaden>0 else ""
        _f = _c1 + crit._kind + _d + _c2
        f = "%-8s" % _f # f must have length 8
        #print "\n\n\n"+ts[ti+y-1] + "\n" + t + "\n" + ts[ti+y+1]
        t = t[:x-4-1] + f + t[x+(8-4-1):]   # 8=len(f), 4=len(shex), -1=relative_writepos
        #print ts[ti+y-1] + "\n" + t + "\n"+ ts[ti+y+1]
        ts[ti+y] = t
        return ts


class TPlains(Terrain_Base):
    def __init__(self):
        Terrain_Base.__init__(self, "Plains")


class TMarsh(Terrain_Base):
    def __init__(self):
        Terrain_Base.__init__(self, "Marsh")
        for hex in [2, 8, 9, 16, 18, 20]:
            self._hazards[hex] = Hazards.BOG

tplains = TPlains()
tmarsh = TMarsh() # std marsh. variant marsh could be 'tmarsh2' or so.

"""singleton"""
_TERRAIN = tplains

def getTerrain():
    global _TERRAIN
    return _TERRAIN

def setTerrain(terrain):
    global _TERRAIN
    _TERRAIN = terrain


######################################################################

# 33 elements, [0..32]
HEX_TO_NAME = (
        [ 'a%s'%n for n in [1,2,3] ] +
        [ 'b%s'%n for n in [1,2,3,4] ] +
        [ 'c%s'%n for n in [1,2,3,4,5] ] +
        [ 'd%s'%n for n in [1,2,3,4,5,6] ] +
        [ 'e%s'%n for n in [1,2,3,4,5] ] +
        [ 'f%s'%n for n in [1,2,3,4] ] +
        [ 'X%s'%n for n in [1,2,3,4,5,6] ] +   # out fields
        [] )


def hexName(hex):
    return HEX_TO_NAME[hex]

class _Critter(object):
    def __init__(self, id, hex, kind, init_power, skill, can_fly=False, can_range=False):
        self._id = id
        # defined by kind
        self._kind = kind
        self._init_power = init_power
        self._skill = skill
        self._can_fly = can_fly
        self._can_range = can_range
        self._kid = KIND_TO_KID[kind]
        self._nativeHazards = []
        # flexible
        self._power = self._init_power
        self._hex = hex

    def clone(self, newHex):
        other = _Critter(self._id,
            newHex, self._kind, self._init_power, self._skill,
            self._can_fly, self._can_range)
        other._power = self._power
        other._kid = self._kid
        other._nativeHazards = self._nativeHazards
        return other

    def isNativeHazard(self, hz):
        return hz in self._nativeHazards

    def ord(self):
        """order by, do eliminate similar army positions.
        uses features of the critter to produce an int value for it that describes
        its state and position on the battlefield. we use
         * the hex,       needed 0..24; easier 0..31; extendable 0..63
         * kind-id,       needed 0..23; easier 0..31; extendable 0..63
         * current power, needed 3..18; easier 0..31; extendable 0..31
         * color, needed 0..1 -- optional, implemented as sign.
        From "needed" you can see if we try hard we coul pack it into 15bit, leaving
        1 bit for the sign/color. This could be done when need for optimization arises.
        For simplicity we use 0..63 for all elements (except color), which still
        would fit in a nice machine-friendly 32bit integer.
        """
        return self._hex | self._kid<<6 | self._power<<12

    def ord(self,  hex):
        """like ord(), but with a different hex"""
        return hex | self._kid<<6 | self._power<<12

    def isEngaged(self, otherArmy):
        """not allowed to move"""
        for otherCritter in otherArmy._critters:
            if self.couldStrike(otherCritter):
                return True
        return False

    def couldStrike(self, otherCritter):
        return getTerrain().couldStrike(self._hex, otherCritter._hex)

    def getMoveHexes(self, blockedHexes1):
        """return possible move-targets for self. hexes that may be free
        due to another citters move are also in the return list and must be filtered
        later.

        # _ | _ || _ | 4 | _ | _ #   init only start with skill.
        # _ | _ || 3 | 4 | 3 | _ #   spread out all positive values to all neighbours, reduced by 1
        # _ | 1 || 3 | 4 | 3 | 2 #   ... or by more if cost is special. only reduce trg number.
        # 0 | 1 || 3 | 4 | 3 | 2 #   all handled, all done.

        version 1.0: snake-like
        version 2.0: flood-fill
        version 2.1: split of enter- and move-costs for fliers
        """
        # init
        blockedHexes = set(blockedHexes1) - set([self._hex])
        _terrain = getTerrain()
        res = {}
        res[ self._hex ] = self._skill  # critter start moving here with its full skill
        _done = False
        _level = self._skill
        while not _done:
            _done = True
            for hexFrom, skillFrom in res.items():
                if skillFrom <= 0: continue
                if skillFrom > _level: continue
                _done = False # something interesting found, more loops needed
                for hexTo in _terrain.getNeighbourHexes(hexFrom):
                    if not self._can_fly:     # fliers can ignore blocked
                        if hexTo in blockedHexes: continue  # walkers might be blocked
                    # calculate movement cost
                    skillTo = skillFrom
                    skillTo -= self.getCreatureMoveCost(hexFrom, hexTo)
                    if not self._can_fly: # fliers dont enter until landing
                        skillTo -= self.getCreatureEnterCost(hexTo)
                    if skillTo < 0: continue
                    if res.get(hexTo, 99) <= skillTo:  # only overwrite if new route is cheaper
                        continue
                    res[hexTo] = skillTo  # set or overwrite
            _level -= 1
        # now we may have to handle fliers that want to "enter" the hex they hover over
        if self._can_fly:     # _now_ fliers try to enter
            for _hex, _skill in [ (h,s)  for h,s in res.items()  if s>0 ]:
                if self.getCreatureEnterCost(_hex) > _skill:
                    del res[_hex]
        # now collect all visited, but remove blocked hexes (ie. for fliers)
        res = set(res.keys()) - set(blockedHexes)
        if getTerrain().isHexMoveoutIfPossible(self._hex):
            res2 = res - set([ self._hex ])
            if res2: return frozenset(res2)
        return frozenset(res)


    def getCreatureEnterCost(self, hexInto):
        """additional costs ontop of getCreatureMoveCost(). 0 on Plains, 0 for fliers in Sand, 1 for non-fliers into Sand."""
        if getTerrain().getHazardOfHex(hexInto) == Hazards.BOG:
            if not self.isNativeHazard(Hazards.BOG):
                return 99
        return 0   # TODO '0' currently a constant

    def getCreatureMoveCost(self, hexFrom, hexTo):
        assert getTerrain().couldStep(hexFrom, hexTo)
        return 1   # TODO '1' currently a constant

    def __repr__(self):
        return "[%s'%s'(%s %s)%s @%s]" % (
            self._kind,
            self._id,
            self._init_power,
            self._skill,
            self._power,
            hexName(self._hex),
            )
    def __str__(self):
        return self.__repr__()

######################################################################

KIND_TO_KID = {
    'Cen' : 1,
    'Ogr' : 2,
    'Lio' : 3,
    'Gar' : 4,
    'Tro' : 5,
    'Ran' : 6,
    'Wyv' : 7,
    'Hyd' : 8,
    'Cyc' : 9,
    # tests
    'Yl1' : 11,
    'Xl1' : 12,
}


class CCentaur(_Critter):
    def __init__(self, id, hex):
        _Critter.__init__(self, id, hex, "Cen", 3, skill=4)

class COgre(_Critter):
    def __init__(self, id, hex):
        _Critter.__init__(self, id, hex, "Ogr", 6, skill=2)
        self._nativeHazards = [ Hazards.BOG ]

class CCyclops(_Critter):
    def __init__(self, id, hex):
        _Critter.__init__(self, id, hex, "Cyc", 9, skill=2)
        self._nativeHazards = [ Hazards.BRAMBLE ]

class CTroll(_Critter):
    def __init__(self, id, hex):
        _Critter.__init__(self, id, hex, "Tro", 8, skill=2)
        self._nativeHazards = [ Hazards.BOG ]

class CRanger(_Critter):
    def __init__(self, id, hex):
        _Critter.__init__(self, id, hex, "Ran", 4, skill=4, can_fly=True, can_range=True)
        self._nativeHazards = [ Hazards.BOG ]

class CWyvern(_Critter):
    def __init__(self, id, hex):
        _Critter.__init__(self, id, hex, "Wyv", 7, skill=3, can_fly=True)
        self._nativeHazards = [ Hazards.BOG ]

class CHydra(_Critter):
    def __init__(self, id, hex):
        _Critter.__init__(self, id, hex, "Hyd", 10, skill=3, can_range=True)
        self._nativeHazards = [ Hazards.BOG ]

class CLion(_Critter):
    def __init__(self, id, hex):
        _Critter.__init__(self, id, hex, "Lio", 5, skill=3)

class CGargoyle(_Critter):
    def __init__(self, id, hex):
        _Critter.__init__(self, id, hex, "Gar", 4, skill=3,  can_fly=True)

class CXlot1(_Critter):
    """A test creature with skill=1"""
    def __init__(self, id, hex):
        _Critter.__init__(self, id, hex, "Xl1", 3, skill=1)

class CYlot1(_Critter):
    """A different test creature with skill=1"""
    def __init__(self, id, hex):
        _Critter.__init__(self, id, hex, "Yl1", 3, skill=1)



######################################################################

class Army(object):
    def __init__(self, color, critters):
        self._color = color
        self._critters = critters
    def __str__(self):
        return self._color + (", ".join(map(str, self._critters)) )


######################################################################

def _moveable_critter_permutations(cs):
    """flyers first"""
    _canFly = lambda c: c._can_fly
    flyers, walkers = _algo.splitby(_canFly, cs)
    # flyers can always move last, walkers need to be permutated
    for it in _algo.xpermutations(walkers):
        yield it + flyers

def _ordOf(crit_hex):
    crit, hex = crit_hex
    return crit.ord(hex)

class MovesGen(object):
    """enumerating all possible moves"""

    def __init__(self, army_me=None, army_other=None):
        if army_me and army_other:
            self._setArmies(army_me, army_other)

    def _setArmies(self, army_me, army_other):
        self._me = army_me
        self._other = army_other
        # init move generator
        self._fixed = []
        self._moveable = []
        # put engaged creatures into the unmovable set
        for critter in self._me._critters:
            if not critter.isEngaged(army_other):
                self._moveable.append( critter )
            else:
                self._fixed.append( critter )
        return

    def moves(self, army_me=None, army_other=None):
        global PROGRESS_COUNT
        PROGRESS_COUNT = 0
        if army_me and army_other:
            self._setArmies(army_me, army_other)
        assert self._me and self._other
        # generate moves
        _critHex = lambda crit: crit._hex
        idents = set() # used to remove dublets
        moved_crits = [ (c, c._hex)  for c in self._fixed ]
        for crits_to_move in _moveable_critter_permutations(self._moveable):
            _blocked_hexes = set( map(_critHex, self._fixed + crits_to_move + self._other._critters) ) # !!! TODO
            for cc in self._moves(crits_to_move , _blocked_hexes):
                res = [ (c, c._hex)  for c in self._fixed ] + cc
                _ident = tuple(sorted(map(_ordOf, res))) # unique identifier for army-situation
                if _ident in idents: continue            # ignore dublet situation
                idents.add(_ident)
                #yield _ident, res
                yield res

    def _moves(self, unmovedCritters, blockedHexes):
        global PROGRESS_COUNT
        if unmovedCritters == []: yield []; return
        idents = set()
        c = unmovedCritters[0]
        for hex in c.getMoveHexes(blockedHexes):
            for cc in self._moves(unmovedCritters[1:], blockedHexes - set([c._hex]) | set([hex])) :
                PROGRESS_COUNT += 1
                res = [(c, hex)] + cc
                _ident = tuple(map(_ordOf, res)) # not sorted(), enough to do it later?
                if _ident in idents: continue
                idents.add(_ident)
                yield res

######################################################################


class MovesGen2(object):
    """new try: not just enumerating all"""

    def __init__(self, army_me=None, army_other=None):
        if army_me and army_other:
            self._setArmies(army_me, army_other)

    def _setArmies(self, army_me, army_other):
        self._me = army_me
        self._other = army_other
        # init move generator
        self._fixed = []
        self._moveable = []
        # put engaged creatures into the unmovable set
        for critter in self._me._critters:
            if not critter.isEngaged(army_other):
                self._moveable.append( critter )
            else:
                self._fixed.append( critter )
        return

    def moves(self, army_me=None, army_other=None):
        global PROGRESS_COUNT
        PROGRESS_COUNT = 0
        if army_me and army_other:
            self._setArmies(army_me, army_other)
        assert self._me and self._other
        # for all movable crits generate their possible target fields
        _critHex = lambda crit: crit._hex
        _blocked = set( map(_critHex, self._fixed + self._other._critters) )
        _moves = set([ (c, c.getMoveHexes(_blocked))  for c in self._moveable ])
        #
        for p in _algo.xpermutations(list(_moves)):
            for c,hexes in p:
                print "   xcv", c, list(_algo.xpermutations(list(hexes)))






######################################################################

def run2():
    print "=== Battlefield Prettyprinter Demo ==="
    #
    setTerrain(tmarsh)
    #
    r1 = CRanger("Rudi", 8)
    r1._power = 3
    army_black = Army('Bk', [ CXlot1('Xaa', 27), CCentaur('Carl', 27), r1 ])
    #
    l1 = CLion("Leo", 22)
    l1._power = 3
    army_red = Army('Rd', [ COgre('Otto', 30), COgre('Ossi', 30), l1 ])
    #
    print army_black
    print army_red
    #
    print getTerrain().pretty(army_black, army_red)


def run3():
    print "=== Terrain movement demo ==="
    setTerrain(tmarsh)
    #
    cyc1 = CCyclops("Carl", 17)
    army_me = Army('Bk', [ cyc1, CLion("Leo", 22), CLion('Lui', 30), CRanger("Rolf", 26) ])
    #
    army_other = Army('Rd', [ CRanger("Rudi", 21) ])
    #
    print "Me:", army_me
    print "Other:", army_other
    print getTerrain().pretty(army_me, army_other)
    #
    mg = MovesGen(army_me, army_other)
    for moves in mg.moves():
        print moves


def run4():
    global PROGRESS_COUNT
    print "=== movement generator speed demo ==="
    setTerrain(tplains)
    rangers = [ CRanger("Rn%d"%hex, hex)  for hex in [17,22,26,16] ]  # worst case: 7 x dexterity-4 (only walking is worse)
    army_me = Army('Bk', rangers)
    army_other = Army('Rd', [ COgre("Otto", 7) ])
    print "Me:", army_me
    print "Other:", army_other
    print getTerrain().pretty(army_me, army_other)
    mg = MovesGen(army_me, army_other)
    count = 0
    for moves in mg.moves():
        count += 1
        if count % 1000 == 0:
            print >>stderr, "generating situations %s (steps:%s)...\r" % (count, PROGRESS_COUNT),
    print >>stderr, "%s situations (internal steps:%d)." % (count, PROGRESS_COUNT)


def run5():
    print "=== New MoveGen ==="
    setTerrain(tmarsh)
    #
    cyc1 = CCyclops("Carl", 17)
    army_me = Army('Bk', [ cyc1, CLion("Leo", 22), CLion('Lui', 30), CRanger("Rolf", 26) ])
    #
    army_other = Army('Rd', [ CRanger("Rudi", 21) ])
    #
    print "Me:", army_me
    print "Other:", army_other
    print getTerrain().pretty(army_me, army_other)
    #
    mg = MovesGen2(army_me, army_other)
    for moves in mg.moves():
        print moves

########################
#
# interesting testcases
#
#   setTerrain(tmarsh)
#
#     army_me = Army('Bk', [ CLion("Leo", 22), COgre("Otto", 17), CLion('Lui', 30),  ])
#     army_other = Army('Rd', [ CRanger("Rudi", 26) ])
#
#     army_me = Army('Bk', [ COgre("Otto", 17), CLion("Leo", 22), CLion('Lui', 30), CRanger("Rolf", 26) ])
#     army_other = Army('Rd', [ CRanger("Rudi", 21) ])
#
#     army_me = Army('Bk', [ Cyclops("Carl", 17), CLion("Leo", 22), CLion('Lui', 30), CRanger("Rolf", 26) ])
#     army_other = Army('Rd', [ CRanger("Rudi", 21) ])


if __name__ == '__main__':
    if False:
        run2()
    elif False:
        run3()
    elif False:
        run4()
    elif True:
        run5()
